﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Configuration;
using System.IO;
using System.Text.RegularExpressions;
using System.Web.UI;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using UrlRouter.Configuration;

namespace UrlRouter.Web
{

    public class UrlRewriteModule : IHttpModule
    {

        const string ItemsVirtualUrl = "UrlRouter.UrlRewriter.VirtualUrl";
        const string ItemsClientQueryString = "UrlRouter.UrlRewriter.ClientQueryString";
        const string ItemsRewriteUrlParameter = "UrlRouter.UrlRewriter.RewriteUrlParameter";
        const string ItemsCachedPathAfterRewrite = "UrlRouter.UrlRewriter.CachedPathAfterRewrite";

        //public const string PhysicalPath = "UrlRewritingNet.UrlRewriter.CachedPathAfterRewrite";

        /// <summary>
        /// 是否只重写路径不存在的虚拟urls
        /// </summary>
        private bool rewriteOnlyVirtualUrls = false;
        /// <summary>
        /// 保存Items[]的前缀
        /// </summary>
        private string _contextItemsPrefix = string.Empty;

        private RewriteRuleCollection _rewrites;
        private ReaderWriterLock lockRewrites = new ReaderWriterLock();
        private ReaderWriterLock lockRedirects = new ReaderWriterLock();

        /// <summary>
        /// 重写规则集属性
        /// </summary>
        internal RewriteRuleCollection Rewrites
        {
            get
            {
                if (_rewrites == null)
                {
                    lock (this)
                    {
                        if (_rewrites == null)
                        {
                            _rewrites = new RewriteRuleCollection();
                        }
                    }
                }
                return _rewrites;
            } 
        }

        private RewriteRuleCollection _redirects;
        /// <summary>
        /// 转向规则集属性
        /// </summary>
        internal RewriteRuleCollection Redirects
        {
            get
            {
                if (_redirects == null)
                {
                    lock (this)
                    {
                        if (_redirects == null)
                        {
                            _redirects = new RewriteRuleCollection();
                        }
                    }
                }
                return _redirects;
            }
        }

        

        private UrlHelper urlHelper;
        /// <summary>
        /// urlHelper用于检查和设置根目录规则的方法
        /// </summary>
        public UrlRewriteModule()
        {
            this.urlHelper = new UrlHelper();
        }
        /// <summary>
        /// 连接url参数自动添加参数连接符 ? &
        /// </summary>
        /// <param name="destinationUrl"></param>
        /// <param name="requestQuerystring"></param>
        /// <returns></returns>
        private string JoinUrlParameter(string destinationUrl, string requestQuerystring)
        {
            int pos = destinationUrl.IndexOf('?');
            if (requestQuerystring.Length > 0)
            {
                if (pos >= 0)
                {
                    destinationUrl += "&" + requestQuerystring;
                }
                else
                {
                    destinationUrl += "?" + requestQuerystring;
                }
            }
            return destinationUrl;
        }
        /// <summary>
        /// 重定向到URL 返回true为已经重定向
        /// </summary>
        /// <param name="app">The app.</param>
        /// <returns>true if redirected, false if not</returns>
        private bool RedirectUrl(HttpApplication app)
        {
            string requestUrl;
            bool redirected = false;
            //先检查目标Redirests
            lockRedirects.AcquireReaderLock(0);
            foreach (RewriteRule rewrite in Redirects)
            {
                if (rewrite.Redirect == RedirectOption.Domain)
                    requestUrl = app.Request.Url.AbsoluteUri;
                else
                    requestUrl = app.Request.RawUrl;

                if (rewrite.IsRewrite(requestUrl))
                {
                    bool includeQueryStringForRewrite = (rewrite.RewriteUrlParameter & RewriteUrlParameterOption.IncludeQueryStringForRewrite) != 0;

                    string urlForRewrite = requestUrl;
                    string requestQuerystring = string.Empty;
                    int pos = requestUrl.IndexOf('?');
                    if (!includeQueryStringForRewrite && pos >= 0)
                    {
                        requestQuerystring = requestUrl.Substring(pos + 1);
                        urlForRewrite = requestUrl.Substring(0, pos);
                    }
                    string destinationUrl = rewrite.RewriteUrl(urlForRewrite);

                    if (includeQueryStringForRewrite)
                        destinationUrl = urlHelper.HandleRootOperator(destinationUrl);
                    else
                        destinationUrl = JoinUrlParameter(urlHelper.HandleRootOperator(destinationUrl), requestQuerystring);

                    StringBuilder sb = new StringBuilder();

                    if (rewrite.Redirect == RedirectOption.Domain)
                    {
                        sb.Append(destinationUrl);
                    }
                    else
                    {
                        sb.Append(app.Request.Url.Scheme);
                        sb.Append("://");
                        sb.Append(app.Request.Url.Authority);
                        sb.Append(destinationUrl);
                    }

                    // Nur f黵 den Fall, dass die Weiterleitung explizit auf permanent
                    // gestellt ist, 301 festlegen - sonst kann das Konsequenzen haben
                    if (rewrite.RedirectMode == RedirectModeOption.Permanent)
                        app.Response.StatusCode = 301;
                    else
                        app.Response.StatusCode = 302;

                    // Location MUST be absolut.
                    //本地必须是绝对地址
                    app.Response.AddHeader("Location", sb.ToString());
                    app.Response.End();
                    redirected = true;
                    break;
                }
            }
            lockRedirects.ReleaseReaderLock();
            return redirected;
        }

        /// <summary>
        /// Rewrites the URL.
        /// 重写URL
        /// </summary>
        /// <param name="app">The app.</param>
        private bool RewriteUrl(HttpApplication app)
        {
            string requestUrl;
            bool rewritten = false;
            // Do the Rewrites
            lockRewrites.AcquireReaderLock(0);
            foreach (RewriteRule rewrite in Rewrites)
            {
                if ((rewrite.Rewrite & RewriteOption.Domain) != 0)
                    requestUrl = app.Request.Url.AbsoluteUri;
                else
                    requestUrl = app.Request.RawUrl;

                if (rewrite.IsRewrite(requestUrl))
                {
                    bool includeQueryStringForRewrite = (rewrite.RewriteUrlParameter & RewriteUrlParameterOption.IncludeQueryStringForRewrite) != 0;

                    string urlForRewrite = requestUrl;
                    string requestQuerystring = string.Empty;
                    int pos = requestUrl.IndexOf('?');
                    if (!includeQueryStringForRewrite && pos >= 0)
                    {
                        requestQuerystring = requestUrl.Substring(pos + 1);
                        urlForRewrite = requestUrl.Substring(0, pos);
                    }

                    app.Context.Items[ItemsClientQueryString] = requestQuerystring;
                    app.Context.Items[ItemsRewriteUrlParameter] = rewrite.RewriteUrlParameter;

                    if ((rewrite.Rewrite & RewriteOption.Domain) != 0)
                    {
                        // Remove domain if exists
                        int slashPos = requestUrl.IndexOf("://");
                        if (slashPos > 0)
                        {
                            slashPos = requestUrl.IndexOf("/", slashPos + 3);
                            if (slashPos > 0)
                            {
                                app.Context.Items[ItemsVirtualUrl] = requestUrl.Substring(slashPos);
                            }
                        }
                    }
                    else
                    {
                        app.Context.Items[ItemsVirtualUrl] = requestUrl;

                    }

                    string destinationUrl = rewrite.RewriteUrl(urlForRewrite);


                    if (includeQueryStringForRewrite)
                        destinationUrl = urlHelper.HandleRootOperator(destinationUrl);
                    else
                        destinationUrl = JoinUrlParameter(urlHelper.HandleRootOperator(destinationUrl), requestQuerystring);
                    // Save original querystring, url and rewrite-parameters for OnPagePreInit()

                    app.Response.StatusCode = 200;
                    app.Context.RewritePath(destinationUrl, false);
                    rewritten = true;
                    break;
                }
            }
            lockRewrites.ReleaseReaderLock();
            return rewritten;
        }
        /// <summary>
        /// Called when [begin request].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void OnBeginRequest(object sender, EventArgs e)
        {
            HttpApplication app = sender as HttpApplication;
            if (!RedirectUrl(app)) //如果没有重定向
            {
                if (rewriteOnlyVirtualUrls && File.Exists(app.Request.PhysicalPath)) return; //如果磁盘有这个文件，终止，不重写

                if (!RewriteUrl(app)) //如果没有重写
                {
                    //如果磁盘没有此请求文件， 并且规则设置的默认页不是空
                    if (!File.Exists(app.Request.PhysicalPath) && !string.IsNullOrEmpty(UrlRewriting.Configuration.DefaultPage))
                    {
                        Uri url = app.Request.Url;
                        if (url.Segments.Length > 0)
                        {
                            //如果url路径段的倒数第二段有“.”说明是请求的文件，退出这里，不做处理
                            if (url.Segments[url.Segments.Length - 1].IndexOf('.') >= 0) return;
                        }
                        StringBuilder sb = new StringBuilder();
                        sb.Append(url.Scheme);
                        sb.Append("://");
                        sb.Append(url.Authority);
                        string lastSeg = "";
                        foreach (string segment in url.Segments)
                        {
                            sb.Append(segment);
                            lastSeg = segment;
                        }
                        if (!lastSeg.EndsWith("/"))
                            sb.Append("/");
                        sb.Append(UrlRewriting.Configuration.DefaultPage);
                        if (!string.IsNullOrEmpty(url.Query))
                        {
                            sb.Append(url.Query);
                        }
                        app.Response.Redirect(sb.ToString());
                        //重定向到规则设置的默认页 包含参数
                    }
                }
            }
        }


        #region IHttpModule members  接口成员方法

        /// <summary>
        /// 实现IHttpModule接口的初始化
        /// </summary>
        /// <param name="context"></param>
        public void Init(System.Web.HttpApplication context)
        {
           

            rewriteOnlyVirtualUrls = UrlRewriting.Configuration.RewriteOnlyVirtualUrls;
            _contextItemsPrefix = UrlRewriting.Configuration.ContextItemsPrefix;

            //初始化的时候,将配置文件中的所有规则组添加到 List集合
            foreach (RewriteSettings rewriteSettings in UrlRewriting.Configuration.Rewrites)
            {
                RewriteRule rewrite = null;
                string providerName = rewriteSettings.Provider;
                if (string.IsNullOrEmpty(providerName))
                {
                    rewrite = UrlRewriting.CreateRewriteRule();
                }
                else
                {
                    rewrite = UrlRewriting.CreateRewriteRule(providerName);
                }
                rewrite.Initialize(rewriteSettings); //到工厂=>实际驱动模式下初始化
                AddRewriteRuleInternal(rewrite);
             }

            context.BeginRequest += new EventHandler(OnBeginRequest); //实现即将开始请求的挂接
            //在ASP.NET开始执行HTTP请求的处理程序之前引发这个事件 此事件为请求处理的挂接
            context.PreRequestHandlerExecute += new EventHandler(OnAppPreRequestHandlerExecute);    //为了防止POST提交或者修改参数的地址不变化
            //请求处理已经完成后的挂接
            context.PostRequestHandlerExecute += new EventHandler(OnAppPostRequestHandlerExecute);  //为了上面的要求,再次重写,以达到地址不变化
        }
        /// <summary>
        /// 实际添加重写规则的方法
        /// </summary>
        /// <param name="rewriteRule"></param>
        internal void AddRewriteRuleInternal(RewriteRule rewriteRule)
        {
            
            
            if (rewriteRule.Redirect == RedirectOption.None)
            {
                lockRewrites.AcquireWriterLock(0);
                Rewrites.Add(rewriteRule); //添加重写规则集
                lockRewrites.ReleaseWriterLock();
            }
            else
            {
                lockRedirects.AcquireWriterLock(0);
                Redirects.Add(rewriteRule); //添加转向规则集
                lockRedirects.ReleaseWriterLock();
            }
        }
        internal void RemoveRewriteRuleInternal(string ruleName)
        {
            lockRewrites.AcquireWriterLock(0);
            Rewrites.RemoveByName(ruleName);
            lockRewrites.ReleaseWriterLock();

            lockRedirects.AcquireWriterLock(0);
            Redirects.RemoveByName(ruleName);
            lockRedirects.ReleaseWriterLock();
        }

        internal void ReplaceRewriteRuleInternal(string ruleName, RewriteRule rewriteRule)
        {
            if (rewriteRule.Redirect == RedirectOption.None)
            {
                lockRewrites.AcquireWriterLock(0);
                Rewrites.ReplaceRuleByName(ruleName, rewriteRule);
                lockRewrites.ReleaseWriterLock();
            }
            else
            {
                lockRedirects.AcquireWriterLock(0);
                Redirects.ReplaceRuleByName(ruleName, rewriteRule);
                lockRedirects.ReleaseWriterLock();
            }
        }

        internal void InsertRewriteRuleBeforeInternal(string positionRuleName, string ruleName, RewriteRule rewriteRule)
        {
            if (rewriteRule.Redirect == RedirectOption.None)
            {
                lockRewrites.AcquireWriterLock(0);
                Rewrites.InsertRuleBeforeName(positionRuleName, ruleName, rewriteRule);
                lockRewrites.ReleaseWriterLock();
            }
            else
            {
                lockRedirects.AcquireWriterLock(0);
                Redirects.InsertRuleBeforeName(positionRuleName, ruleName, rewriteRule);
                lockRedirects.ReleaseWriterLock();
            }
        }

        public void Dispose()
        {
        }

        #endregion
        /// <summary>
        /// 执行结束后
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnAppPostRequestHandlerExecute(object sender, EventArgs e)
        {
            HttpApplication app = sender as HttpApplication;
            string cachedPath = (string)app.Context.Items[ItemsCachedPathAfterRewrite];
            if (!string.IsNullOrEmpty(cachedPath))
            {
                app.Context.RewritePath(cachedPath);  //?从共享数据中重写 防止POST后的地址变化
            }
        }
        /// <summary>
        /// 请求执行前
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnAppPreRequestHandlerExecute(object sender, EventArgs e)
        {
            HttpApplication app = sender as HttpApplication;
            System.Web.UI.Page page = app.Context.CurrentHandler as System.Web.UI.Page;
            if (page != null)
            {
                page.PreInit += new EventHandler(OnPagePreInit);
            }
        }
        /// <summary>
        /// 执行前的预初始化
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnPagePreInit(object sender, EventArgs e)
        {
            HttpContext context = HttpContext.Current;
            string virtualUrl = (string)context.Items[ItemsVirtualUrl];
            if (!string.IsNullOrEmpty(virtualUrl))
            {
                int pos = virtualUrl.IndexOf('?');
                if (pos >= 0)
                {
                    virtualUrl = virtualUrl.Substring(0, pos);
                }

                RewriteUrlParameterOption option = (RewriteUrlParameterOption)context.Items[ItemsRewriteUrlParameter];
                string fullQueryString = context.Request.QueryString.ToString();
                context.Items[ItemsCachedPathAfterRewrite] = context.Request.Path + "?" + fullQueryString;

                if ((option & RewriteUrlParameterOption.StoreInContextItems) != 0)
                {
                    foreach (string key in context.Request.QueryString.AllKeys)
                    {
                        context.Items[_contextItemsPrefix + key] = context.Request.QueryString[key];
                    }
                }

                string clientQueryString = (string)context.Items[ItemsClientQueryString];
                if ((option & RewriteUrlParameterOption.ExcludeFromClientQueryString) != 0)
                {
                    context.RewritePath(virtualUrl, string.Empty, clientQueryString, true);
                    Page page = sender as Page;
                    clientQueryString = page.ClientQueryString;
                }

                if ((option & RewriteUrlParameterOption.StoreInContextItems) == 0)
                {
                    context.RewritePath(virtualUrl, string.Empty, fullQueryString, true);
                }
                else if ((option & RewriteUrlParameterOption.ExcludeFromClientQueryString) == 0)
                {
                    context.RewritePath(virtualUrl, string.Empty, clientQueryString, true);
                }
            }
        }

    }
}
